package rhc.common.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.net.ssl.SSLContext;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.DeflateDecompressingEntity;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * @author lenovo1
 *
 */
public class HttpUtil {

	private static final String CODE = "UTF-8";

	/**
	 * 获取连接超时时间
	 */
	protected static final int connectionRequestTimeout = 2000;
	/**
	 * 连接超时时间
	 */
	protected static final int connectTimeout = 5000;
	/**
	 * 获取数据超时时间
	 */
	protected static final int socketTimeout = 30000;

	/**
	 * 重试次数
	 */
	protected static final int retryNum = 2;

	private static final CloseableHttpClient httpClient = createHttpClient();
	private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);

	/**
	 * 发送post请求
	 * 
	 * @param url
	 * @param json
	 * @return
	 * @throws Exception 
	 */
	public static String doPostBody(String url, String body) throws Exception {
		return doPostBody(url,body,"application/text");
	}
	
	/**
	 * 发送post请求
	 * 
	 * @param url
	 * @param json
	 * @return
	 * @throws Exception 
	 */
	public static String doPostBody(String url, String body,String contentType) throws Exception {
		HttpRequestBase postMethod = new HttpPost(url);
		String result = null;
		CloseableHttpResponse response = null;
		StringEntity entity = null;
		logger.debug("发起post请求 {}:{}:{}", url , body , contentType);
		entity = new StringEntity(body, CODE);
		entity.setContentType(contentType);
		((HttpPost) postMethod).setEntity(entity);
		/* 发送请求，并获取响应对象 */
		result = executeRequest(postMethod);
		logger.debug("post请求 {}:{} 请求到数据 {}",url , body,result);
		return result;
	}

	/**
	 * 发送post请求
	 * 
	 * @param url
	 * @param json
	 * @return
	 * @throws Exception 
	 */
	public static String doGet(String url) throws Exception {
		return sendGetParameter(url,"");
	}

	/**
	 * 向指定URL发送GET方法的请求
	 * 
	 * @param url
	 *            发送请求的URL
	 * @param param
	 *            请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
	 * @return URL 所代表远程资源的响应结果
	 * @throws Exception 
	 */
	public static String sendGetParameter(String url, String param) throws Exception {
		
		HttpGet get=null;
		String result=null;
		try {
			String fullUrl=null;
			fullUrl+=StringUtils.isEmpty(param)?url:("?"+param);
			get=new HttpGet(fullUrl);
		} catch (Exception e) {
			logger.error("发送GET请求出现异常！{}" , e.getMessage());
			return result;
		}
		logger.info("发起get请求 {}:{}" , url , param);
		/* 发送请求，并获取响应对象 */
		result = executeRequest(get);
		return result;
	}

	/**
	 * 向指定URL发送GET方法的请求
	 * 
	 * @param url
	 *            发送请求的URL
	 * @param param
	 *            请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
	 * @return URL 所代表远程资源的响应结果
	 * @throws Exception 
	 */
	public static String sendGetParameter(String url, Map<String,String> param) throws Exception {
		
		HttpGet get=null;
		String result=null;
		try {
			url+="?";
			StringBuilder temp=new StringBuilder();
			if(null !=param){
				for(String key:param.keySet()){
					temp.append(key).append("=").append(param.get(key));
				}
				url+=temp.toString();
			}
			get=new HttpGet(url+"?"+param);
		} catch (Exception e) {
			logger.error("发送GET请求出现异常！{}" , e.getMessage());
			return result;
		}
		logger.info("发起get请求 {}:{}" ,url , param);
		/* 发送请求，并获取响应对象 */
		result = executeRequest(get);
		return result;
	}
	
	/**
	 * post参数
	 * 
	 * @param requestUrl
	 * @param parameters
	 * @return
	 * @throws Exception 
	 */
	public static String sendPostParameter(String requestUrl, Map<String, String> parameters) throws Exception {
		HttpRequestBase postMethod = new HttpPost(requestUrl);
		String result = null;
		if (null != parameters) {
			List<org.apache.http.NameValuePair> nvps = new ArrayList<org.apache.http.NameValuePair>(parameters.size());
			Set<String> keys = parameters.keySet();
			Iterator<String> iterator = keys.iterator();
			while (iterator.hasNext()) {
				String key = iterator.next();
				String value = parameters.get(key);
				if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) {
					continue;
				}
				nvps.add(new BasicNameValuePair(key, value));
			}
			try {
				((HttpPost) postMethod).setEntity(new UrlEncodedFormEntity(nvps, CODE));
			} catch (UnsupportedEncodingException e) {
				logger.error("创建postparameter时出错：{}:{}" , requestUrl , parameters);
				e.printStackTrace();
				return null;
			}
		}
		logger.info("发起post请求 {}:{}" , requestUrl , parameters);
		/* 发送请求，并获取响应对象 */
		result = executeRequest(postMethod);
		return result;
	}
	
	/**获取一个末日超时设置的request
	 * @param url
	 * @return
	 */
	public static HttpGet getGetRequest(String url){
		return getGetRequest(url,null,null,null);
	}
	
	/**获取一个getRequest，并设置超时
	 * @param url
	 * @param connectionRequestTimeout_
	 * @param connectTimeout_
	 * @param socketTimeout_
	 * @return
	 */
	public static HttpGet getGetRequest(String url,Integer connectionRequestTimeout_,Integer connectTimeout_,
			Integer socketTimeout_){
		HttpGet get=new HttpGet(url);
		requestSetConfig(get,connectionRequestTimeout_,connectTimeout_,socketTimeout_);
		return get;
	}

	/**
	 * 获取一个get请求的request
	 * 
	 * @param url
	 *            请求url
	 * @param connectionRequestTimeout
	 *            获取连接超时时间
	 * @param connectTimeout
	 *            连接服务器超时时间
	 * @param socketTimeout
	 *            传输数据超时时间
	 * @return
	 */
	public static HttpRequestBase requestSetConfig(HttpRequestBase request, Integer connectionRequestTimeout_, Integer connectTimeout_,
			Integer socketTimeout_) {
		if(null == request){
			return null;
		}
		RequestConfig config = getConfig(null,
				null == connectionRequestTimeout_ ? connectionRequestTimeout : connectionRequestTimeout_,
				null == connectTimeout_ ? connectTimeout : connectTimeout_,
				null == socketTimeout_ ? socketTimeout : socketTimeout_);
		request.setConfig(config);
		return request;
	}

	/**执行request，获取返回的re's
	 * @param request
	 * @return
	 * @throws Exception 
	 */
	public static HttpResponse executeRequestGetResponse(HttpRequestBase request) throws Exception {
		CloseableHttpResponse response = null;
		Exception throwE=null;
		try {
			response = httpClient.execute(request);
			int statusCode= response.getStatusLine().getStatusCode();
			if (statusCode != HttpStatus.SC_OK) {
				logger.error("request 返回异常代码：{},{}",request.getURI().toString(),statusCode);
			}
		} catch (UnsupportedEncodingException e) {
			logger.error("request 请求异常：{},,不支持编码异常，字符编码有问题:{}",request.getURI().toString(), e.getMessage());
			throwE=e;
		} catch (IOException e) {
			logger.error("request 请求异常：{},IO异常:{}",request.getURI().toString(), e.getMessage());
			throwE=e;
		} catch (Exception e) {
			logger.error("request 请求异常：{},异常：{}",request.getURI().toString(), e.getMessage());
			throwE=e;
		} finally{
			if(null != throwE){
				throw throwE;
			}
		}
		return response;
	}
	
	/**将输入流里的数据写入输出流
	 * @param input
	 * @param output
	 * @throws IOException 
	 */
	public static void fromInputStreamToOutputStream(InputStream input,OutputStream output) throws IOException{
		
		if(null == input || null == output){
			return;
		}
		byte[] buffer = new byte[1024];
		int r = 0;
		while ((r = input.read(buffer)) != -1) {
			output.write(buffer, 0, r);
		}
	}

	/** 获取请求的配置
	 * @param proxy
	 * @param connectionRequestTimeout
	 * @param connectTimeout
	 * @param socketTimeout
	 * @return
	 */
	public static RequestConfig getConfig(HttpHost proxy, int connectionRequestTimeout, int connectTimeout,
			int socketTimeout) {
		org.apache.http.client.config.RequestConfig.Builder builder = RequestConfig.custom()
				//
				.setAuthenticationEnabled(true)
				//
				.setConnectionRequestTimeout(connectionRequestTimeout)//
				.setConnectTimeout(connectTimeout)// /
				.setSocketTimeout(socketTimeout)//
				.setRedirectsEnabled(true)//
				.setRelativeRedirectsAllowed(true)//
				.setMaxRedirects(5)//
				.setCookieSpec(CookiePolicy.BROWSER_COMPATIBILITY);//
		if (proxy != null) {
			builder.setProxy(proxy);
		}
		return builder.build();
	}

	/**执行一个请求
	 * @param request
	 * @return
	 */
	/**执行一个请求
	 * @param request
	 * @return
	 * @throws Exception 
	 */
	private static String executeRequest(HttpRequestBase request) throws Exception{
		CloseableHttpResponse response = null;
		String pageContent = null;
		long beginTime=System.currentTimeMillis();
		Exception exception= null;
		try {
			response = httpClient.execute(request);
			int statusCode= response.getStatusLine().getStatusCode();
			if (statusCode != HttpStatus.SC_OK) {
				logger.error("request 返回异常代码：{},{}",request.getURI().toString(),statusCode);
			}
			pageContent = toString(response);
			return pageContent;
		} catch (UnsupportedEncodingException e) {
			logger.error("request 请求异常：{},不支持编码异常，字符编码有问题:{}",request.getURI().toString(), e.getMessage());
			exception =e ;
		} catch (IOException e) {
			logger.error("request 请求异常：{},IO异常:{}",request.getURI().toString(), e.getMessage());
			exception =e ;
		} catch (Exception e) {
			logger.error("request 请求异常：{},异常：{}",request.getURI().toString(), e.getMessage());
			exception =e ;
		} finally {
			logger.info("{} 耗时：{}",request.toString(),System.currentTimeMillis()-beginTime);
			request.releaseConnection();
			if(null != exception){
				throw exception;
			}
		}
		return pageContent;
	}

	/**
	 * 创建一个 httpclient
	 * 
	 * @return
	 */
	private static CloseableHttpClient createHttpClient() {

		final int maxPerRoute = 200;
		final int maxTotal = maxPerRoute * 100;

		PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
		connManager.setDefaultMaxPerRoute(maxPerRoute);
		connManager.setMaxTotal(maxTotal);

		SSLContext sslContext = null;
		try {
			sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
				// 信任所有
				public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
					return true;
				}
			}).build();
		} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
			e.printStackTrace();
		}
		SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
		// 当连接超时或读取超时且没有达到最大重试次数时重试
		HttpRequestRetryHandler myretryhandler = new HttpRequestRetryHandler() {
			@Override
			public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {

				if ((exception instanceof SocketTimeoutException || exception instanceof ConnectTimeoutException)
						&& executionCount < retryNum) {
					return true;
				}
				return false;
			}
		};
		HttpClientBuilder httpbuilder = HttpClientBuilder.create()//
				.setConnectionManager(connManager)//
				.setDefaultSocketConfig(SocketConfig.custom()//
						.setSoKeepAlive(true)//
						.setSoReuseAddress(true)//
						.build()//
		)//
				.setDefaultRequestConfig(RequestConfig.custom()//
						.setAuthenticationEnabled(true)//
						.setConnectionRequestTimeout(connectionRequestTimeout)//
						.setConnectTimeout(connectTimeout)// /
						.setSocketTimeout(socketTimeout)//
						.setRedirectsEnabled(true)//
						.setRelativeRedirectsAllowed(true)//
						.setMaxRedirects(5)//
						.build()//
		).setSSLSocketFactory(sslsf);
		httpbuilder.setRetryHandler(myretryhandler);
		CloseableHttpClient httpClient = httpbuilder.build();

		return httpClient;
	}

	/**
	 * 将返回的内容转为byte，支持压缩流
	 * 
	 * @param response
	 * @return
	 * @throws IOException
	 */
	protected static byte[] toBytes(HttpResponse response) throws IOException {

		if (response == null) {
			return null;
		}

		AcceptEncodingEnum acceptEncoding = AcceptEncodingEnum.NOTHING;

		Header[] contentEncodings = response.getHeaders("Content-Encoding");

		if (contentEncodings != null && contentEncodings.length != 0) {
			for (Header header : contentEncodings) {
				if (StringUtils.containsIgnoreCase(header.getValue(), "gzip")) {
					acceptEncoding = AcceptEncodingEnum.GZIP;
					break;
				}

				if (StringUtils.containsIgnoreCase(header.getValue(), "deflate")) {
					acceptEncoding = AcceptEncodingEnum.DEFLATE;
					break;
				}
			}
		}

		byte[] bytes = null;
		switch (acceptEncoding) {
		case GZIP:
			bytes = EntityUtils.toByteArray(new GzipDecompressingEntity(response.getEntity()));
			break;
		case DEFLATE:
			bytes = EntityUtils.toByteArray(new DeflateDecompressingEntity(response.getEntity()));
			break;
		default:
			bytes = EntityUtils.toByteArray(response.getEntity());
			break;
		}

		return bytes;
	}

	/**
	 * 获取返回的html，
	 * 
	 * @param response
	 * @param charset
	 * @return
	 * @throws IOException
	 */
	protected static String toString(HttpResponse response) throws IOException {
		return toString(response, "UTF-8");
	}

	/**
	 * 获取返回的html，
	 * 
	 * @param response
	 * @param charset
	 * @return
	 * @throws IOException
	 */
	protected static String toString(HttpResponse response, String charset) throws IOException {

		if (response == null) {
			return null;
		}

		if (StringUtils.isBlank(charset)) {
			charset = "UTF-8";
		}

		byte[] bytes = toBytes(response);

		if (ArrayUtils.isEmpty(bytes)) {
			return null;
		}

		String content = new String(bytes, charset);

		String docCharset = null;

		// 从header中获取编码
		Header[] contentTypes = response.getHeaders("Content-Type");
		if (contentTypes != null && contentTypes.length != 0) {
			for (Header header : contentTypes) {
				if (StringUtils.containsIgnoreCase(header.getValue(), "charset=")) {
					docCharset = StringUtils.substringAfter(header.getValue().toLowerCase(), "charset=");
					docCharset = StringUtils.substringBefore(docCharset, ";");
					docCharset = StringUtils.trimToEmpty(docCharset);
					break;
				}
			}
		}
		// 获取到了编码,没有获取到则默认utf8编码
		if (!(StringUtils.isBlank(docCharset) || docCharset.equalsIgnoreCase(charset))) {
			// 有些文档会出现这个编码，但java中是GB2312，否则会出异常
			if (docCharset.equalsIgnoreCase("GBK2312")) {
				docCharset = "GB2312";
			}
			try {
				content = new String(bytes, docCharset);
			} catch (Exception e) {
				logger.error(e.toString());
			}
		}

		return content;
	}
}

enum AcceptEncodingEnum {
	GZIP, DEFLATE, NOTHING
}
